# -*- coding: utf-8 -*-

import re

from PyQt4 import QtCore

CODEC = "UTF-8"


class QuestionContainer(QtCore.QObject):

    def __init__(self, questionList):
        QtCore.QObject.__init__(self)
        self.__giftFile = QtCore.QString()
        self.__questions = {}
        self.__changed = False
        self.__questionList = questionList


    def __len__(self):
        return len(self.__questions)


    def __iter__(self):
        for question in self.__questions.values():
            yield question


    def isChanged(self):
        return self.__changed


    def setChanged(self, changed=True):
        self.emit(QtCore.SIGNAL("questionsChanged()"))
        self.__changed = changed


    def clear(self):
        self.questions = []


    def add(self, question):
        #if id(question) in self.__questions:
            #return False
        self.__questions[id(question)] = question
        self.setChanged()

        return True


    def delete(self, questionId):
        if self.__questions.has_key(questionId):
            del self.__questions[questionId]
        self.setChanged()


    def save(self, giftFile=QtCore.QString()):
        if not giftFile.isEmpty():
            self.__giftFile = giftFile
        if self.__giftFile.endsWith(".txt"):
            return self.saveGiftTextStream()

        return False, "Failed to save: invalid file extension"


    def load(self, giftFile=QtCore.QString()):
        if not giftFile.isEmpty():
            self.__giftFile = giftFile
        if self.__giftFile.endsWith(".txt"):
            return self.loadGiftTextStream()

        return False, "Failed to save: invalid file extension"


    def filename(self):
        return self.__giftFile


    @staticmethod
    def formats():
        return "*.txt"


    def saveGiftTextStream(self):
        """ ulozeni otazek do textoveho souboru v GIFT formatu """

        error = None
        fh = None
        try:
            fh = QtCore.QFile(self.__giftFile)
            if not fh.open(QtCore.QIODevice.WriteOnly | QtCore.QIODevice.Text):
                raise IOError, unicode(fh.errorString())
            stream = QtCore.QTextStream(fh)
            stream.setCodec(CODEC)
            for question in self.__questions.values():
                stream << question.getData() << "\n"

        except (IOError, OSError), e:
            error = "Failed to save: %s" % e
        finally:
            if fh is not None:
                fh.close()
            if error is not None:
                return False, error
            self.setChanged(True)

            return True, self.tr("Saved %1 questions to %2").arg(len(self.__questions)).arg(QtCore.QFileInfo(self.__giftFile).fileName())


    def loadGiftTextStream(self):
        """ otevira textovy soubor s otazkami v GIFT souboru """

        error = None
        fh = None
        try:
            fh = QtCore.QFile(self.__giftFile)
            if not fh.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text):
                raise IOError, unicode(fh.errorString())
            stream = QtCore.QTextStream(fh)
            stream.setCodec(CODEC)
            #stream.skipWhiteSpaces()
            # TODO smaznout vsechny widgety otazek i fyzicky
            #self.clear()
            # pocitam radky, pokud narazim na chybu, vim na kterem radku
            lino = 0
            while not stream.atEnd():
                line = stream.readLine()
                lino += 1
                # Aaaah nirvana .. tyhle dva radky resi prazdne radky, predtim kdyz byl na konci nebo mezi otazkami prazdny radek navic, sel skript do kytek, tohle to uspesne resi
                # kdyz je radek prazdny, cyklus se vrati na zacatek a zkusi to znovu
                if line.isEmpty():
                    continue

                question = title = text = None
                answers = []

                # kouknem jetli radek neni komentar (zacina znaky "//")
                if line.startsWith("//"):
                    # komentar ignorujeme
                    line = stream.readLine()
                    lino += 1

                # pokud radek zacina dvojteckou a nekonci na {, coz uvozuje zacatek odpovedi, znamena to, ze titulek a zadani otazky jsou na rucnych radcich
                if line.startsWith("::") and not line.endsWith("{"):
                    # aktulani radek je tedy titulek
                    title = line
                    # dalsi nacteny by melo byt zadani a tim padem musi koncit znakem { a dal jsou odpovedi
                    line = stream.readLine()
                    lino += 1
                    if not line.endsWith("{"):
                        raise ValueError, "ocekavan zacatek otazky"
                    # promenna se zadanim otazky
                    text = line
                # ale pokud jsou je vse na jednom radku, resi cely ukol else blok
                else:
                    # radek se zadanim otazky, obsahuje titulek (nemusi byt) a zadani otazky (povinne)
                    questionLine = line.left(line.lastIndexOf("{"))
                    # vykuchame z nej titulek (pokud existuje) a otazku (musi existovat)
                    title, text = self.getQuestionTask(QtCore.QString(questionLine).trimmed())

                line = stream.readLine()
                lino += 1
                while not stream.atEnd():
                    answers.append(line.trimmed())
                    line = stream.readLine()
                    lino += 1
                    if line.trimmed().endsWith("}"):
                        if title is None or text is None:
                            raise ValueError, QtCore.QObject.tr("Incomplete question!")
                        else:
                            type = self.detectQuestionType(answers)
                            question = self.__questionList[type]()
                            question.type = type
                            question.title = title or QtCore.QString()
                            question.text = text or QtCore.QString()
                            answers = self.makeNiceAnwers(type, answers)
                            #question.addAnswer()
                            for answer in answers:
                                question.addAnswer(answer)
                                #print answer
                                #print answer
                            #question.setAnswers(answers)
                            question.update()
                            self.add(question)
                        break
                else:
                    raise ValueError, "ocekavany odpovedi"
                # ted bychom meli byt na prazdnem radku ktery oddeluje otazky
                line = stream.readLine().toUtf8()
                lino += 1

        #except (NameError, IOError, OSError, ValueError), e:
        except Exception, e:
            error = "Failed to load: %s on line %d" % (e, lino)
            #print "failed: %s" % e
        finally:
            if fh is not None:
                fh.close()
            if error is not None:
                return False, error
            self.__changed = False
            return True, self.tr("Loaded %1 questions from %2.").arg(len(self.__questions)).arg(QtCore.QFileInfo(self.__giftFile).fileName())


    def getQuestionTask(self, text):
        """ vrati zadani/titulek otazky"""

        # tady to bude trochu slozitejsi, zadani otazky muze obsahovat i titulek, ale nemusi.
        # Takze nejdriv je volitelny titulek prefixovany znaky :: a pak zadani otazky opet prefixovane ::
        # Titulek byt nemusi a kdyz neni, tak samotne zadani nemusi mit :: prefix

        # rozdelime text, uvidme kolik casti nam z toho vypadne
        try:
            parts = text.split("::", QtCore.QString.SkipEmptyParts)
            # tady je to myslim celkem jasne
            if len(parts) == 2:
                questionTitle, questionText = parts
            elif len(parts) == 1:
                questionText = parts.first()
                questionTitle = QtCore.QString()
            else:
                raise ValueError, "nepovedlo se ziskat title a text otazky"
        except Exception, e:
            return False, "chybicka pri zpracovani otazky: %s" % e

        #print "text: %s %s , titulek: %s %s" % (questionText.trimmed().toUtf8(), type(questionText), questionTitle.trimmed().toUtf8(), type(questionTitle))

        return questionTitle.trimmed(), questionText.trimmed()
        #return "foo", "bar"


    def detectQuestionType(self, answers):
        """ zjisti typ otazky ze zadaneho listu odpovedi """

        #miss = []

        matchingRegexp = re.compile(".*->.*", re.UNICODE)
        questionType = None

        # pocet nalezu tildy
        tildaHit = 0
        # pocet nalezu rovnitka
        equationHit = 0
        # pocet otazek
        answersCount = len(answers)

        for answer in answers:
            try:
                # prvni znak aktualne resene odpovedi
                answerPrefix = answer[0]

                # nejdriv test, zda se nejedna o Matching type
                if answerPrefix == "=":
                    if re.findall(matchingRegexp, answer):
                        questionType = "matching"
                        # hledani skoncilo, bye bye
                        break

                if answerPrefix == "~":
                    tildaHit += 1
                elif answerPrefix == "=":
                    equationHit += 1
                elif answerPrefix == "F" or answerPrefix == "T":
                    questionType = "trueFalse"
                    break

                if tildaHit == answersCount:
                    questionType = "multipleAnswer"
                elif equationHit == answersCount:
                    questionType = "shortAnswer"
                else:
                    questionType = "multipleChoice"

            except KeyError, e:
                # TODO: osetrit co teda s otazkou kdyz neurcime typ
                questionType = "unkown"
                #if self.debug:
                print "ouch, chybicka - %s" % e
                    #miss.append("%s : %s" % (error, answer))

        return questionType


    def makeNiceAnwers(self, type, answers):
        """ odstrani GIFT znaky, nahradi je za patricna data (True/False) vypreparuje procenta """

        percentRegexp = QtCore.QRegExp("^%(-?\\d*\\.?\\d+)%")
        specialChars = {QtCore.QString("=") : True,
                        QtCore.QString("~") : False}

        niceAnswers = []

        for answer in answers:
            niceAnswer = []
            if type == "multipleChoice":
                # vlozime podle prvni znaku True nebo False pro (od)oznaceni radio buttonu
                niceAnswer.append(specialChars[answer[0]])
            # vykopnu prvni znak (~ a = jsou znacky GIFT formatu)
            if answer.startsWith("~") or answer.startsWith("="):
                answer.remove(0,1)
            if type == "multipleChoice" or type == "multipleAnswer":
                if answer.contains(percentRegexp):
                    percentMatch = percentRegexp.indexIn(answer)
                    # procentualni hodnota kterou chceme (bez GIFT znacek %, proste jenom ciso
                    percent = percentRegexp.cap(1)
                    # vlozime hodnotu
                    niceAnswer.append(percent)
                    # odstranime procenta z textu. prvni argument je zacatek textu s procenty, druhy je konec
                    answer.remove(percentRegexp.cap(), percentRegexp.matchedLength())
                else:
                    # pokud procenta nejsou, vlozime misto nich None
                    niceAnswer.append(None)
            # pokud mame matchingtype, musime jeste otazku rozkuchat pres "->" oddelovac a misto oddelovace mezi ty dve casti flaknout treba None
            if type == "matching":
                try:
                    first, second = answer.split("->")
                except ValueError:
                    first = answer.trimmed()
                    second = self.tr("Error")
                niceAnswer.append(first.trimmed())
                niceAnswer.append(None)
                niceAnswer.append(second.trimmed())
            else:
                # a vlozime text k ostatnim datum
                niceAnswer.append(answer)

            niceAnswers.append(niceAnswer)

        return niceAnswers
